GitHub ActionsでAWS Step Functions LocalとJestによるステートマシンのMockテストを実行する
こんにちは、CX事業本部 IoT事業部の若槻です。
前回の下記エントリでは、AWS Step Functions LocalによるMockテストをJestで実行してみました。
今回は、同じくAWS Step Functions LocalとJestによるState MachineのMockテストをGitHub Actionsで実行してみました。
やってみた
実装のファイル構成は次のようになります。
$ tree . ├── .github │ └── workflows │ └── ci.yml ├── jest.config.js ├── package-lock.json ├── package.json ├── test │ └── mock │ └── stepfunctions │ ├── MyStateMachine │ │ ├── MockConfigFile.json │ │ ├── MyStateMachine.asl.json │ │ └── MyStateMachine.test.ts │ └── docker-compose.yml └── tsconfig.json
導入しているパッケージです。
$ npm ls --depth=0 project@0.1.0 /path/to/project ├── @types/jest@28.1.6 ├── @types/node@18.6.2 ├── jest@28.1.3 ├── ts-jest@28.0.7 ├── ts-node@10.9.1 └── typescript@4.7.4
Test files
test/mock/stepfunctions/MyStateMachine
配下ではテスト内容に関するファイルを管理しています。
MockConfigFile.json
は、テスト対象のState Machine(後述)に対するMock ResponseのConfigです。これによりAWS ServiceからTaskへのレスポンスをMockすることができます。またレスポンスは複数のパターンを記述し、テスト実行時に使用することが可能です。
{ "StateMachines": { "MyStateMachine": { "TestCases": { "FugaPathTest": { "GetParameter": "GetParameterFugaMockedSuccess" }, "NotFugaPathTest": { "GetParameter": "GetParameterNotFugaMockedSuccess" } } } }, "MockedResponses": { "GetParameterFugaMockedSuccess": { "0": { "Return": { "Parameter": { "Arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/hoge", "DataType": "text", "LastModifiedDate": "2022-07-26T05:38:43.052Z", "Name": "hoge", "Type": "String", "Value": "fuga", "Version": 4 } } } }, "GetParameterNotFugaMockedSuccess": { "0": { "Return": { "Parameter": { "Arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/hoge", "DataType": "text", "LastModifiedDate": "2022-07-26T05:38:43.052Z", "Name": "hoge", "Type": "String", "Value": "nyao", "Version": 4 } } } } } }
MyStateMachine.asl.json
は、テスト対象となるState MachineのASL(Amazon States Language)定義のファイルです。
{ "Comment": "A description of my state machine", "StartAt": "GetParameter", "States": { "GetParameter": { "Type": "Task", "Next": "Choice", "Parameters": { "Name": "hoge" }, "Resource": "arn:aws:states:::aws-sdk:ssm:getParameter", "ResultSelector": { "hoge.$": "$.Parameter.Value" } }, "Choice": { "Type": "Choice", "Choices": [ { "Variable": "$.hoge", "StringMatches": "fuga", "Next": "FugaPath" } ], "Default": "NotFugaPath" }, "FugaPath": { "Type": "Pass", "End": true }, "NotFugaPath": { "Type": "Pass", "End": true } } }
MyStateMachine.test.ts
は、Jestのテストコードです。上述のASLファイルを使用してコンテナ上にState Machieを作成して実行し、実行が成功していることおよびMock Responseで定義したテストケース毎に想定したパスを経由していることを確認しています。
import { SFNClient, GetExecutionHistoryCommand, StartExecutionCommand, CreateStateMachineCommand, DescribeExecutionCommand, ExecutionStatus, HistoryEvent, DeleteStateMachineCommand, } from '@aws-sdk/client-sfn'; import TEST_TARGET_ASL = require('./MyStateMachine.asl.json'); const DUMMY_AWS_REGION = 'us-east-1'; const DUMMY_AWS_ACCOUNT = '123456789012'; const STEP_FUNCTIONS_LOCAL_ENDPOINT = 'http://localhost:8083'; const DUMMY_EXECUTION_ROLE_ARN = `arn:aws:iam::${DUMMY_AWS_ACCOUNT}:role/DummyRole`; const STATE_MACHINE_NAME = 'MyStateMachine'; const DUMMY_STATE_MACHINE_ARN = `arn:aws:states:${DUMMY_AWS_REGION}:${DUMMY_AWS_ACCOUNT}:stateMachine:${STATE_MACHINE_NAME}`; const sfnClient = new SFNClient({ region: DUMMY_AWS_REGION, credentials: { accessKeyId: 'dummy', secretAccessKey: 'dummy' }, endpoint: STEP_FUNCTIONS_LOCAL_ENDPOINT, //AWS Step Functions Localのエンドポイントを指定 }); //State Machineの作成 beforeAll(async () => { await sfnClient.send( new CreateStateMachineCommand({ name: STATE_MACHINE_NAME, roleArn: DUMMY_EXECUTION_ROLE_ARN, definition: JSON.stringify(TEST_TARGET_ASL), }), ); }); //State Machineの削除 afterAll(async () => { await sfnClient.send( new DeleteStateMachineCommand({ stateMachineArn: DUMMY_STATE_MACHINE_ARN, }), ); }); //State Machine実行の開始および履歴取得 const startExecutionAndGetExecutionHistory = async ( testCase: string, ): Promise<HistoryEvent[]> => { const executionResult = await sfnClient.send( new StartExecutionCommand({ name: testCase, stateMachineArn: `${DUMMY_STATE_MACHINE_ARN}#${testCase}`, }), ); const executionArn = executionResult.executionArn; let executionStatus = 'RUNNING'; while (executionStatus === 'RUNNING') { await new Promise((r) => setTimeout(r, 1000)); const res = await sfnClient.send( new DescribeExecutionCommand({ executionArn: executionArn, }), ); if (res.status !== 'RUNNING') executionStatus = res.status as ExecutionStatus; } const executionHistory = await sfnClient.send( new GetExecutionHistoryCommand({ executionArn: executionArn, includeExecutionData: true, }), ); return executionHistory.events as HistoryEvent[]; }; describe('MyStateMachine', () => { test('FugaPathTest', async () => { const executionHistory = await startExecutionAndGetExecutionHistory( 'FugaPathTest', ); //State Machine実行の分岐がFugaPathを経由していることを確認 expect(executionHistory[8].stateEnteredEventDetails?.name).toBe('FugaPath'); //State Machine実行が成功していることを確認 expect(executionHistory[10].type).toBe('ExecutionSucceeded'); }); test('NotFugaPathTest', async () => { const executionHistory = await startExecutionAndGetExecutionHistory( 'NotFugaPathTest', ); //State Machine実行の分岐がNotFugaPathを経由していることを確認 expect(executionHistory[8].stateEnteredEventDetails?.name).toBe( 'NotFugaPath', ); //State Machine実行が成功していることを確認 expect(executionHistory[10].type).toBe('ExecutionSucceeded'); }); });
Docker Compose file
AWS Step Functions LocalのコンテナImageを実行するためのDocker Composeの構成ファイルです。実行時にMockConfigFile.json
をマウントします。
version: '3' services: sfn_local: image: amazon/aws-stepfunctions-local volumes: - ./MyStateMachine/MockConfigFile.json:/home/StepFunctionsLocal/MockConfigFile.json environment: SFN_MOCK_CONFIG: /home/StepFunctionsLocal/MockConfigFile.json ports: - 8083:8083
Workflow file
GitHub ActionsのWorkflowのファイルです。docker compose
コマンドでAWS Step Functions Local環境を立ち上げ、Jestコマンドでテストを実行しています。
name: CI on: workflow_dispatch jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Cache CDK Dependency uses: actions/cache@v3 id: cache_cdk_dependency_id env: cache-name: cache-cdk-dependency with: path: node_modules key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }} restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}- - name: Clean install Dependency if: ${{ steps.cache_cdk_dependency_id.outputs.cache-hit != 'true' }} run: npm ci - name: Run container image working-directory: test/mock/stepfunctions run: docker compose up -d - name: Run Step Functions mock test run: npx jest test/mock/stepfunctions
動作確認
Workflowを実行すると、テストが実行されSuccessしました。
Run npx jest test/mock/stepfunctions npx jest test/mock/stepfunctions shell: /usr/bin/bash -e {0} PASS test/mock/stepfunctions/MyStateMachine/MyStateMachine.test.ts (10.315 s) MyStateMachine ✓ FugaPathTest (1180 ms) ✓ NotFugaPathTest (1046 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 10.426 s Ran all test suites matching /test\/mock\/stepfunctions/i.
おわりに
GitHub ActionsでAWS Step Functions LocalとJestによるステートマシンのMockテストを実行してみました。
これでコードのPush時などにStep FunctionsのMockテストをCI Workflowの一環として実行できるようになりました。次回はさらにAWS CDKで開発をしている場合にASLを自動でテストに使用できるようにしたいです。
参考
以上